import { StandardStorage, Content, Entry, StandardStorageOptions } from '@/file_storage/standard_storage'
import { IBrowserFileSystem, ReadFileType } from '@/common/filesystem'
import { posix as path } from '@/common/path'
import { sanitizeFileName } from '@/common/utils'
import { isFirefox } from '@/common/dom_utils'
import Ext from '@/common/web_extension'
import { singletonGetterByKey } from '@/common/ts_utils'
import { getBrowserFileSystem } from '@/file_storage/delegate'

export interface IWithLinkStorage {
  getLink: (fileName: string) => Promise<string>
}

export type BrowserFileSystemsStandardStorageOptions = StandardStorageOptions & {
  baseDir?: string;
  extensions: string[];
  shouldKeepExt: boolean;
  transformFileName?: (path: string) => string;
}

// 继承StandardStorage
export class BrowserFileSystemStandardStorage extends StandardStorage implements IWithLinkStorage {
  protected fs: IBrowserFileSystem
  protected baseDir: string // 根目录
  protected extensions: string[] // 文件拓展
  protected shouldKeepExt: boolean // 是否应该保留扩展名
  protected transformFileName = (path: string) => path // 转化文件名称

  constructor(opts: BrowserFileSystemsStandardStorageOptions) {
    super({
      encode: opts.encode,
      decode: opts.decode
    })
    const { extensions, shouldKeepExt, transformFileName, baseDir = 'share' } = opts

    if (!baseDir || baseDir === '/') {
      throw new Error(`Invalid baseDir, ${baseDir}`)
    }

    if (transformFileName) {
      this.transformFileName = transformFileName
    }

    this.fs = getBrowserFileSystem()
    this.baseDir = baseDir
    this.extensions = extensions
    this.shouldKeepExt = shouldKeepExt

    // Note: create the folder in which we will store files
    this.fs.getDirectory(baseDir, true)
  }
  // 获取图片链接
  public getLink(filePath: string): Promise<string> {
    if (!isFirefox()) {
      // chrome
      const tmp = Ext.runtime.getURL('temporary')
      const link = `filesystem:${tmp}/${this.filePath(filePath)}`
      return Promise.resolve(link + '?' + new Date().getTime())
    } else {
      // Note: Except for Chrome, the filesystem API we use is a polyfill from idb.filesystem.js
      // idb.filesystem.js works great but the only problem is that you can't use 'filesystem:' schema to retrieve that file
      // so here, we have to convert the file to data url
      return <Promise<string>>this.read(filePath, 'DataURL')
    }
  }
  // 读文件
  public read(filePath: string, type: ReadFileType): Promise<Content> {
    const fullPath = this.filePath(filePath)
    const relativePath = path.relative(this.dirPath('/'), fullPath)

    return this.fs.readFile(fullPath, type)
      .then(
        intermediate => this.decode(intermediate, relativePath, type),
        error => {
          if (error.message.indexOf("A requested file or directory could not be found") !== -1) {
            throw new Error(`Error #301: File not found (file names are case-sensitive): ${filePath}`)
          }
          return Promise.reject(error)
        }
      )
  }
  // 统计文件信息
  public stat(entryPath: string, isDir?: boolean): Promise<Entry> {
    const name = path.basename(entryPath)
    const dir = path.dirname(entryPath)
    const fullPath = isDir ? this.dirPath(entryPath) : this.filePath(entryPath)
    const relativePath = path.relative(this.dirPath('/'), fullPath)

    return this.fs.existsStat(fullPath)
      .then(({ isFile, isDirectory }) => {
        // Note: idb.filesystem.js (we use it as polyfill for firefox) doesn't support getMetadata on folder yet
        // so we simply set size/lastModified to empty value for now.

        if (!isFile) {
          // 目录信息
          return {
            dir,
            name,
            fullPath,
            relativePath,
            isFile,
            isDirectory,
            size: 0,
            lastModified: new Date(0)
          }
        }

        return this.fs.getMetadata(fullPath, isDirectory)
          .then((meta) => {
            // 文件信息
            return {
              dir,
              name,
              fullPath,
              relativePath,
              isFile,
              isDirectory,
              size: meta.size,
              lastModified: meta.modificationTime
            }
          })
      })
  }

  // 目录列表
  protected __list(directoryPath: string = '/', brief: boolean = false): Promise<Entry[]> {
    // TODO: Ignore brief param for browser fs for now
    const convertName = (entryName: string, isDirectory: boolean) => {
      return this.shouldKeepExt || isDirectory ? entryName : this.removeExt(entryName)
    }

    return this.ensureBaseDir()
      .then(() => this.fs.list(
        this.dirPath(directoryPath)
      ))
      .then(fileEntries => {
        const ps = fileEntries.map(fileEntry => {
          return this.stat(fileEntry.fullPath, fileEntry.isDirectory)
            .then((stat) => ({
              ...stat,
              name: this.transformFileName(convertName(stat.name, fileEntry.isDirectory))
            }))
        })

        return Promise.all(ps)
          .then(list => {
            list.sort((a, b) => {
              if (a.name < b.name) return -1
              if (a.name > b.name) return 1
              return 0
            })

            this.totalCount = list.length
            this.displayedCount = list.length

            return list
          })
      })
  }

  // 写入文件覆盖旧内容
  protected __write(filePath: string, content: any): Promise<void> {
    return this.ensureBaseDir()
      .then(() => this.remove(filePath))
      .catch(() => { /* Ignore any error */ })
      .then(() => this.encode(content, filePath))
      .then((encodedContent: any) => this.fs.writeFile(this.filePath(filePath, true), encodedContent))
      .then(() => { })
  }
  // 写入文件覆盖旧内容
  protected __overwrite(filePath: string, content: any): Promise<void> {
    return this.__write(filePath, content)
  }
  // 删除文件
  protected __removeFile(filePath: string): Promise<void> {
    return this.fs.removeFile(this.filePath(filePath))
  }
  // 删除空目录
  protected __removeEmptyDirectory(directoryPath: string): Promise<void> {
    return this.fs.rmdir(this.dirPath(directoryPath))
  }

  // 移动文件
  protected __moveFile(filePath: string, newPath: string): Promise<void> {
    return this.fs.moveFile(
      this.filePath(filePath),
      this.filePath(newPath, true)
    )
      .then(() => { })
  }

  // 复制文件
  protected __copyFile(filePath: string, newPath: string): Promise<void> {
    return this.fs.copyFile(
      this.filePath(filePath),
      this.filePath(newPath, true)
    )
      .then(() => { })
  }

  // 创建目录
  protected __createDirectory(directoryPath: string): Promise<void> {
    return this.fs.getDirectory(this.dirPath(directoryPath, true), true)
      .then(() => { })
  }

  // 目录路径
  public dirPath(dir: string, shouldSanitize: boolean = false) {
    const path = this.getPathLib() // 获取path库
    const absPath = (() => {
      if (this.isStartWithBaseDir(dir)) {
        return dir
      } else {
        return path.join('/', this.baseDir, dir)
      }
    })() // 绝对路径

    const dirName = path.dirname(absPath)
    const baseName = path.basename(absPath)
    const sanitized = shouldSanitize ? sanitizeFileName(baseName) : baseName

    return path.join(dirName, sanitized)
  }

  // 浏览器为false
  public isWin32Path(): boolean {
    return false
  }

  // 文件路径
  public filePath(filePath: string, shouldSanitize: boolean = false) {
    const dirName = path.dirname(filePath)
    const baseName = path.basename(filePath)
    const sanitized = shouldSanitize ? sanitizeFileName(baseName) : baseName
    const existingExt = path.extname(baseName)
    const ext = this.extensions[0]
    const finalFileName = existingExt && existingExt.substr(1).toLowerCase() === ext.toLowerCase() ? sanitized : (sanitized + '.' + ext)

    if (this.isStartWithBaseDir(dirName)) {
      return path.join(dirName, this.transformFileName(finalFileName))
    } else {
      return path.join('/', this.baseDir, dirName, this.transformFileName(finalFileName))
    }
  }

  // 是否以baseDir开始
  private isStartWithBaseDir(str: string): boolean {
    return str.indexOf('/' + this.baseDir) === 0
  }

  // 如果没有baseDir目录则创建baseDir
  private ensureBaseDir(): Promise<void> {
    return this.fs.ensureDirectory(this.baseDir)
      .then(() => { })
  }

  // 删除文件名中的拓展名 a.json=>a
  private removeExt(fileNameWithExt: string) {
    const name: string = path.basename(fileNameWithExt)
    const ext: string = path.extname(fileNameWithExt)
    const i: number = name.lastIndexOf(ext)

    return name.substring(0, i)
  }
}


export const getBrowserFileSystemStandardStorage = singletonGetterByKey(
  (opts: BrowserFileSystemsStandardStorageOptions) => {
    return (opts && opts.baseDir) || 'share'
  },
  (opts: BrowserFileSystemsStandardStorageOptions) => {
    return new BrowserFileSystemStandardStorage(opts)
  }
)
